﻿using System;
using System.Linq;
using System.Collections.Generic;
using Bling.DSL;
using Bling.Core;
using Microsoft.WindowsAPICodePack.DirectX.Direct3D10;
using Microsoft.WindowsAPICodePack.DirectX.Direct3D;
using Microsoft.WindowsAPICodePack.DirectX.DXGI;
//using Controls = Microsoft.WindowsAPICodePack.DirectX.Controls;
//using Microsoft.WindowsAPICodePack.DirectX.Controls;
using Microsoft.WindowsAPICodePack.DirectX.DirectXUtilities;
using DXUtil = Microsoft.WindowsAPICodePack.DirectX.DirectXUtilities;
//using Microsoft.WindowsAPICodePack.DirectX.DirectXUtilities;
//using Bling.Shaders.DX;
using Bling.Vecs;
using linq = Microsoft.Linq.Expressions;
using Bling.Linq;
using Bling.Util;
using Bling.Matrices;
using System.Runtime.InteropServices;
using Bling.Graphics;
using DEffect = Microsoft.WindowsAPICodePack.DirectX.Direct3D10.Effect;


namespace Bling.DX {
  using System.IO;
  using Microsoft.WindowsAPICodePack.DirectX.WindowsImagingComponent;
  public class RenderDeviceImpl {
    internal readonly D3DDevice Device;
    internal readonly SwapChain SwapChain;

    internal readonly Dictionary<RenderTarget, RenderTargetEntry> RenderTargets = new Dictionary<RenderTarget,RenderTargetEntry>();
    internal class RenderTargetEntry : IRenderTargetMarker, IDisposable {
      public RenderTargetView RenderTargetView;
      public Viewport Viewport;
      public Texture2D DepthStencil;
      public DepthStencilView DepthStencilView;

      public void Dispose() {
        if (RenderTargetView != null)
          RenderTargetView.Dispose();
        if (DepthStencil != null)
          DepthStencil.Dispose();
        if (DepthStencilView != null)
          DepthStencilView.Dispose();
        RenderTargetView = null;
        DepthStencil = null;
        DepthStencilView = null;
      }

      public void MakeDepthStencil(RenderDeviceImpl Outer, Vec2<uint> Size) {

        Texture2DDescription descDepth = new Texture2DDescription() {
          Width = Size[0],
          Height = Size[1],
          MipLevels = 1,
          ArraySize = 1,
          Format = Format.D32_FLOAT,
          SampleDescription = new SampleDescription() {
            Count = 1,
            Quality = 0
          },
          BindFlags = BindFlag.DepthStencil,
        };

        DepthStencil = Outer.Device.CreateTexture2D(descDepth);
        // Create the depth stencil view
        DepthStencilViewDescription depthStencilViewDesc = new DepthStencilViewDescription() {
          Format = descDepth.Format,
          ViewDimension = DepthStencilViewDimension.Texture2D
        };
        DepthStencilView = Outer.Device.CreateDepthStencilView(DepthStencil, depthStencilViewDesc);
      }
      public void Activate(D3DDevice Device) {
        Device.OM.SetRenderTargets(new RenderTargetView[] { RenderTargetView }, DepthStencilView);
        Device.RS.SetViewports(new Viewport[] { Viewport });
      }
    }
    public readonly RenderDevice RenderDevice;
    public RenderDeviceImpl(RenderDevice RenderDevice) {
      this.RenderDevice = RenderDevice;
    }

    public RenderDeviceImpl(RenderDevice RenderDevice, IntPtr Handle, Vec2<uint> Size) : this(RenderDevice) {
      Device = D3DDevice1.CreateDeviceAndSwapChain1(Handle, out SwapChain);
      SetViews(Size);
    }
    private void SetViews(Vec2<uint> Size) {
      if (!RenderTargets.ContainsKey(RenderDevice.Display)) {
        RenderTargets[RenderDevice.Display] = new RenderTargetEntry();
      }
      var display = RenderTargets[RenderDevice.Display];
      using (Texture2D pBuffer = SwapChain.GetBuffer<Texture2D>(0)) {
        display.RenderTargetView = Device.CreateRenderTargetView(pBuffer);
      }
      display.Viewport = new Viewport() {
        Width = Size[0],
        Height = Size[1],
        MinDepth = 0.0f,
        MaxDepth = 1.0f,
        TopLeftX = 0,
        TopLeftY = 0
      };
      display.MakeDepthStencil(this, Size);
    }

    public void Resize(Vec2<uint> Size) {
      var display = RenderTargets[RenderDevice.Display];
      display.Dispose();
      SwapChainDescription sd = SwapChain.Description;
      SwapChain.ResizeBuffers(sd.BufferCount, Size[0], Size[1], sd.BufferDescription.Format, sd.Flags);
      SetViews(Size);
      // Update the projection matrix
      //projectionMatrix = DXUtil.Camera.MatrixPerspectiveFovLH((float)Math.PI * 0.25f, ((float)host.ActualWidth / (float)host.ActualHeight), 0.5f, 100.0f);
      //projectionVariable.Matrix = projectionMatrix;

    }

    public RenderDeviceImpl(RenderDevice RenderDevice, Vec2<uint> Size) : this(RenderDevice) {
      Device = D3DDevice.CreateDevice();
      {
        var display = new RenderTargetEntry();

        Texture2DDescription textureDescription = new Texture2DDescription() {
          Width = (uint)Size[0],
          Height = (uint)Size[1],
          ArraySize = 1,
          MipLevels = 1,
          Format = Format.R8G8B8A8_UNORM,
          Usage = Usage.Default,
          BindFlags = BindFlag.ShaderResource | BindFlag.RenderTarget,
          SampleDescription = new SampleDescription() {
            Count = 1,
            Quality = 0,
          },
        };
        var pBuffer = Device.CreateTexture2D(textureDescription);
        display.RenderTargetView = Device.CreateRenderTargetView(pBuffer);
        display.Viewport = new Viewport() {
          Width = Size[0],
          Height = Size[1],
          MinDepth = 0.0f,
          MaxDepth = 1.0f,
          TopLeftX = 0,
          TopLeftY = 0
        };
        display.MakeDepthStencil(this, Size);
        RenderTargets[RenderDevice.Display] = display;
      }
    }
    public IntPtr GetHandle() {
      return RenderTargets[RenderDevice.Display].RenderTargetView.NativeInterface;
    }

    internal RenderTargetEntry LoadTarget(RenderTarget Target) {
      if (RenderTargets.ContainsKey(Target)) return RenderTargets[Target];

      (Target is RenderTargetTexture<D2>.Target).Assert();
      var Texture = ((RenderTargetTexture<D2>.Target)Target).Texture;
      CreateTexture(Texture);
      return RenderTargets[Target];
    }
    internal Action PreAction;
    internal Action RenderAction;

    private static readonly ColorRgba BlackColor = new ColorRgba { Red = 0.0F, Green = 0.0F, Blue = 0.0F, Alpha = 1.0F };
    public void ClearTarget() {
      var re = RenderTargets[RenderDevice.Display];
      // clear display first. 
      Device.ClearRenderTargetView(re.RenderTargetView, BlackColor);
      if (re.DepthStencilView != null)
        Device.ClearDepthStencilView(re.DepthStencilView, ClearFlag.Depth, 1.0f, (byte)0);
    }
    public void DoSwap() {
      SwapChain.Present(0, PresentFlag.Default);
    }
    public void DoPost(Action Post) {
      Post();
    }
    public void ResetSO() {
      Device.SO.SetTargets(new D3DBuffer[] { null }, new uint[] { 0 });
    }

    public void InitRender() {
      var DeviceE = linq.Expression.Constant(Device, typeof(D3DDevice));
      {
        var Eval = new UniformEval(this) { };


        var RenderCodeBlock = new List<linq.Expression>();

        RenderCodeBlock.Add(linq.Expression.Call(linq.Expression.Constant(this),
          typeof(RenderDeviceImpl).GetMethod("ClearTarget", new Type[0] { })));

        foreach (var e in RenderDevice.Entries) {
          (e.Technique.Effect != null).Assert();
          var Effect0 = LoadEffect(e.Technique.Effect, e.Uniforms);
          (e.Technique != null).Assert();
          Eval.Technique = Effect0[e.Technique];
          {
            var SetTarget = Eval.DoEval(e.Target).Value;
            SetTarget = linq.Expression.Convert(SetTarget, typeof(RenderTargetEntry));
            SetTarget = linq.Expression.Call(SetTarget, "Activate", new Type[] { }, DeviceE);
            RenderCodeBlock.Add(SetTarget);
          }
          var StreamOut = e.StreamOut == null ? null : Eval.DoEval(e.StreamOut.Provider).Value;
          if (StreamOut != null) {
            StreamOut = linq.Expression.Convert(StreamOut, typeof(BufferEntry));
            StreamOut = linq.Expression.Call(StreamOut, "StreamOut", new Type[] { }, DeviceE);
            RenderCodeBlock.Add(StreamOut);
          }
          {
            var DoRender = Eval.DoEval(e.Buffer.Provider).Value;
            DoRender = linq.Expression.Convert(DoRender, typeof(BufferEntry));
            DoRender = linq.Expression.Call(DoRender, "Render", new Type[] { }, DeviceE, linq.Expression.Constant(Effect0[e.Technique], typeof(EffectTechnique)));
            RenderCodeBlock.Add(DoRender);
          }
          if (StreamOut != null)
            RenderCodeBlock.Add(linq.Expression.Call(linq.Expression.Constant(this), typeof(RenderDeviceImpl).GetMethod("ResetSO", new Type[0] { })));
          if (e.Post != null)
            RenderCodeBlock.Add(linq.Expression.Call(linq.Expression.Constant(this), typeof(RenderDeviceImpl).GetMethod("DoPost", new Type[1] { typeof(Action) }), linq.Expression.Constant(e.Post)));
        }
        RenderCodeBlock.Add(linq.Expression.Call(linq.Expression.Constant(this), typeof(RenderDeviceImpl).GetMethod("DoSwap", new Type[0] { })));

        var PstB = (RenderCodeBlock.Count == 0) ? linq.Expression.Empty() : (RenderCodeBlock.Count == 1) ? RenderCodeBlock[0] : linq.Expression.Block(RenderCodeBlock.ToArray());
        PstB = Eval.PopScope(PstB);
        RenderAction = linq.Expression.Lambda<Action>(PstB).Compile();
      }
      {
        var Pre = new List<linq.Expression>();
        foreach (var e in Effects) {
          if (e.Value.RefreshUniforms != null) {
            Pre.Add(linq.Expression.Invoke(linq.Expression.Constant(e.Value.RefreshUniforms, typeof(Action))));
          }
        }
        var PreB = (Pre.Count == 0) ? linq.Expression.Empty() : (Pre.Count == 1) ? Pre[0] : linq.Expression.Block(Pre.ToArray());
        PreAction = linq.Expression.Lambda<Action>(PreB).Compile();
      }
    }
    public struct EffectKey { public Effect Effect; }
    public struct EffectEntry {
      public DEffect Effect;
      public Action RefreshUniforms;

      public EffectTechnique this[Effect.Technique Technique] {
        get {
          return Effect.GetTechniqueByName(Technique.Name);
        }
      }
    }

    internal readonly Dictionary<D3DResource, View> Views = new Dictionary<D3DResource, View>();
    internal readonly Dictionary<object, D3DResource> Resources = new Dictionary<object, D3DResource>();

    internal interface INativeTexture : ITexture {
      ShaderResourceView View { get; }
    }
    internal class NativeTexture<DIM> : Texture<DIM>, INativeTexture where DIM : Dim1D<DIM> {
      public ShaderResourceView View { get; private set; }
      public NativeTexture(ShaderResourceView View) { this.View = View; }
    }
    internal class UniformEval : LinqEval<UniformEval> {
      public readonly RenderDeviceImpl Outer;
      public EffectTechnique Technique = null;
      public UniformEval(RenderDeviceImpl Outer) { this.Outer = Outer; }

      public override Eval<UniformEval>.Info<T> Table<T>(T[] Values, Expr<int> Index) {
        if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Texture<>)) {
          for (int i = 0; i < Values.Length; i++) {
            var Texture = (Texture)(object)Values[i];
            ShaderResourceView srv;
            if (Texture is IImageTexture) {
              srv = Outer.LoadImage((IImageTexture)Texture);
            } else if (Texture is IRenderTargetTexture) {
              srv = Outer.ResourceViewFor(Outer.CreateTexture((IRenderTargetTexture)Texture));
            } else if (Texture is IUserTexture) {
              srv = Outer.ResourceViewFor(Outer.CreateTexture((IUserTexture)Texture));
            } else throw new NotSupportedException();
            var DIM = typeof(T).GetGenericArguments()[0];
            var nt = typeof(NativeTexture<>).MakeGenericType(DIM).GetConstructor(new Type[] { typeof(ShaderResourceView) }).
              Invoke(new object[] { srv });
            Values[i] = (T)nt;
          }
        }
        return base.Table<T>(Values, Index);
      }

      public override Eval<UniformEval>.Info<T> Constant<T>(Constant<T> constant) {
        if (typeof(T).IsSubclassOf(typeof(Texture))) {
          var Texture = (Texture) ((IConstant)constant).BaseValue;
          ShaderResourceView srv;
          if (Texture is IImageTexture) {
            srv = Outer.LoadImage((IImageTexture)Texture);
          } else if (Texture is IRenderTargetTexture) {
            srv = Outer.ResourceViewFor(Outer.CreateTexture((IRenderTargetTexture) Texture));
          } else if (Texture is IUserTexture) {
            srv = Outer.ResourceViewFor(Outer.CreateTexture((IUserTexture)Texture));
          } else throw new NotSupportedException();
          // we now expect the type of the eval...to be NativeTexture, not Texture. 
          var DIM = typeof(T).GetGenericArguments()[0];
          var nt = typeof(NativeTexture<>).MakeGenericType(DIM).GetConstructor(new Type[] { typeof(ShaderResourceView) }).
            Invoke(new object[] { srv });
          return base.Constant(new Constant<T>((T) nt));
        } else if (typeof(T) == typeof(IBufferMarker)) { // doesn't matter on the input.
          var Buffer = (BaseBuffer)(object)((Constant<T>)constant).Value;
          var BufferEntry = Outer.LoadBuffer(Buffer, Technique);
          return base.Constant<T>(new Constant<T>((T)(object) BufferEntry));
        } else if (typeof(T) == typeof(IStreamOutBufferMarker)) {
          var Buffer = (BaseBuffer)(object)((Constant<T>)constant).Value;
          var BufferEntry = Outer.LoadBuffer(Buffer, Technique);
          return base.Constant<T>(new Constant<T>((T)(object)BufferEntry));
        } else if (typeof(T) == typeof(IRenderTargetMarker)) {
          var Target = (RenderTarget)(object)constant.Value;
          var RenderTargetEntry = Outer.LoadTarget(Target);
          return base.Constant<T>(new Constant<T>((T)(object)RenderTargetEntry));
        }
        return base.Constant<T>(constant);
      }
    }
    internal readonly Dictionary<EffectKey, EffectEntry> Effects = new Dictionary<EffectKey, EffectEntry>();


    internal EffectEntry LoadEffect(Effect Effect, Dictionary<SignatureEntry,Expr> Uniforms) {
      var key = new EffectKey() { Effect = Effect, /*Uniforms = Uniforms */};
      int hc = key.GetHashCode();

      if (Effects.ContainsKey(key)) return Effects[key];
      DEffect Effect0;
      {
        var CodeOut = new StringWriter();
        Effect.Output(CodeOut);
        var Source = CodeOut.ToString();
        Effect0 = Device.CreateEffectFromSource(Source, 0, null);
      }
      // now load the uniforms.
      var assigns = new List<linq.Expression>();
      var eval = new UniformEval(this);
      foreach (var Property in Uniforms) {
        var Var = Effect0.GetVariableByName(Property.Key.Name);
        var value = eval.DoEval(Property.Value).Value;
        //value = eval.PopScope(value);
        var tinfo = Property.Key.Type.FormatForType();
        if (Property.Key.Type.IsSubclassOf(typeof(Texture))) { // convert to native texture, access shader resource.
          value = linq.Expression.Property(linq.Expression.Convert(value, typeof(INativeTexture)), "View");
          value = linq.Expression.Call(
            linq.Expression.Constant(Var.AsShaderResource(), typeof(EffectShaderResourceVariable)),
            "SetResource", new Type[] { }, value);
        } else if (tinfo.Kind == TypeInfo.Kinds.Scalar) {
          if (Property.Key.Type != tinfo.TypeK)
            value = linq.Expression.Convert(value, tinfo.TypeK);
          var MethodName = "Set" + tinfo.CName;
          var Scalar0 = linq.Expression.Constant(Var.AsScalar(), typeof(EffectScalarVariable));

          value = linq.Expression.Call(Scalar0, 
            typeof(EffectScalarVariable).GetProperty(tinfo.CName + "Value").GetSetMethod(), 
            value);
        } else if (tinfo.Kind == TypeInfo.Kinds.Vector || tinfo.Kind == TypeInfo.Kinds.Matrix) {
          var Elements = linq.Expression.Parameter(tinfo.TypeK.MakeArrayType(), "Elements");
          var Composite = linq.Expression.Parameter(tinfo.Arity.ToType, "Composite");

          var assigns0 = new linq.Expression[tinfo.Arity.Count + 3];
          //(TypeInfo.Arity.Count <= 4).Assert();
          assigns0[0] = linq.Expression.Assign(Elements, linq.Expression.NewArrayBounds(tinfo.TypeK,
            linq.Expression.Constant(Math.Max(4, tinfo.Arity.Count), typeof(int))));
          assigns0[1] = linq.Expression.Assign(Composite, value);
          for (int j = 0; j < tinfo.Arity.Count; j++) {
            var LHS = linq.Expression.ArrayAccess(Elements, linq.Expression.Constant(j, typeof(int)));

            var RHS = tinfo.Arity.PropertyFor(j)(Composite);
            if (tinfo.Arity.TypeOfK != tinfo.TypeK)
              RHS = linq.Expression.Convert(RHS, tinfo.TypeK);
            assigns0[j + 2] = linq.Expression.Assign(LHS, RHS);
          }
          if (tinfo.Kind == TypeInfo.Kinds.Vector) {
            var Vector = linq.Expression.Constant(Var == null ? null : Var.AsVector(), typeof(EffectVectorVariable));

            assigns0[tinfo.Arity.Count + 2] =
              linq.Expression.Call(Vector, 
                typeof(EffectVectorVariable).GetProperty(tinfo.CName + "Vector").GetSetMethod(), 
                linq.Expression.New(typeof(Vector4F).GetConstructor(new Type[] { typeof(float[]) }), Elements));


          } else if (tinfo.Kind == TypeInfo.Kinds.Matrix) {
            var Matrix = linq.Expression.Constant(Var == null ? null : Var.AsMatrix(), typeof(EffectMatrixVariable));
            assigns0[tinfo.Arity.Count + 2] =
              linq.Expression.Call(Matrix, 
              typeof(EffectMatrixVariable).GetProperty("Matrix").GetSetMethod(),
              linq.Expression.New(typeof(Matrix4x4F).GetConstructor(new Type[] { typeof(float[]) }), Elements));
          } else throw new NotSupportedException();
          value = linq.Expression.Block(new linq.ParameterExpression[] {
              Elements, Composite,
            }, assigns0);
        } else throw new NotSupportedException();
        assigns.Add(value);
      }
      {
        linq.Expression block;
        if (assigns.Count > 0)
          block = linq.Expression.Block(assigns);
        else 
          block = linq.Expression.Empty();
        block = eval.PopScope(block);
        var action = linq.Expression.Lambda<Action>(block).Compile();
        var entry = new EffectEntry() {
          Effect = Effect0,
          RefreshUniforms = action,
        };
        Effects[key] = entry;
        return entry;
      }
    }
    internal class BufferEntry : IBufferMarker, IStreamOutBufferMarker {
      public InputLayout InputLayout;
      public D3DBuffer[] Buffers;
      public D3DBuffer IndexBuffer = null;
      public uint[] Strides;
      public PrimitiveTopology Topology = PrimitiveTopology.TriangleList;
      public Action RefreshVertices = () => { }; // on each step, contrast to initialization. 
      public Action<D3DDevice> DoDraw;
      public void Render(D3DDevice Device, EffectTechnique Technique) {
        Device.IA.SetInputLayout(InputLayout);
        Device.IA.SetPrimitiveTopology(Topology);
        var offsets = new uint[Strides.Length];
        for (int i = 0; i < Strides.Length; i++) offsets[i] = 0;

        Device.IA.SetVertexBuffers(0, Buffers, Strides, offsets);
        if (IndexBuffer != null)
          Device.IA.SetIndexBuffer(IndexBuffer, Format.R32_UINT, 0);
        TechniqueDescription techDesc = Technique.Description;
        for (uint p = 0; p < techDesc.Passes; ++p) {
          Technique.GetPassByIndex(p).Apply();
          DoDraw(Device);
        }
      }
      public void StreamOut(D3DDevice Device) {
        Device.SO.SetTargets(Buffers, new uint[] { 0 });
      }
    }
    internal readonly Dictionary<BaseBuffer, BufferEntry> Buffers = new Dictionary<BaseBuffer, BufferEntry>();
    //internal readonly Dictionary<IndexBuffer, D3DBuffer> IndexBuffers = new Dictionary<IndexBuffer, D3DBuffer>();


    private class Slot {
      public readonly List<SignatureEntry> Properties = new List<SignatureEntry>();
      //public uint Stride;
      public D3DBuffer Buffer;
      public int AllocatedBytes = 0;
    }
    private class Slots {
      public readonly List<Slot> Elements = new List<Slot>();
      public Slot this[int idx] { get { return Elements[idx]; } }
      public Slot this[bool IsInstance] {
        get {
          if (!IsInstance) return this[0];
          if (Elements.Count < 2) Elements.Add(new Slot());
          return this[1];
        }
      }
      public Slots() {
        Elements.Add(new Slot());
      }
    }

    internal BufferEntry LoadBuffer(BaseBuffer Buffer, EffectTechnique Technique) {
      if (this.Buffers.ContainsKey(Buffer)) return this.Buffers[Buffer];
      // compute the layout.
      // assume all vertices are static at first, only one buffer, we can change this later as we classify dynamic dependencies.
      InputLayout InputLayout = null;
      Action<D3DDevice> DoDraw;
      var Slots = new Slots();

      //var Properties = new List<List<SignatureEntry>>();
      //var Strides = new List<uint>();
      {
        var Layout = new List<InputElementDescription>();
        {
          //var AllocatedBytes = 0;
          foreach (var Property in Buffer.InputSig) {
            var Slot = Slots[Property.IsInstance];
            Slot.Properties.Add(Property);

            var InputSlot = (uint)Slots.Elements.IndexOf(Slot);
            var InputSlotClass = 
              Property.IsInstance ? InputClassification.PerInstanceData : InputClassification.PerVertexData; 
            uint InputDataStepRate = (uint) (Property.IsInstance ? 1 : 0); 
            var TypeInfo = Property.Type.FormatForType();
            var arity = VecUtils.Query(Property.Type).Dim as IDim2D;
            if (arity != null && (arity.ColCountI == 1 || arity.RowCountI == 1)) arity = null;
            if (arity != null) {
              for (int k = 0; k < arity.RowCountI; k++) {
                Layout.Add(new InputElementDescription() {
                  SemanticName = Property.Semantic.Name,
                  SemanticIndex = (uint)(Property.Semantic.Index + k),
                  Format = TypeInfo.Format(0),
                  InputSlot = InputSlot,
                  AlignedByteOffset = (uint)Slot.AllocatedBytes,
                  InputSlotClass = InputSlotClass,
                  InstanceDataStepRate = (uint)InputDataStepRate,
                });
                Slot.AllocatedBytes += (TypeInfo.SizeInBytes / arity.RowCountI);
              }
            } else if (TypeInfo.SizeInBytes <= 4 * 4) {
              Layout.Add(new InputElementDescription() {
                SemanticName = Property.Semantic.Name,
                SemanticIndex = (uint)Property.Semantic.Index,
                Format = TypeInfo.Format(0),
                InputSlot = InputSlot,
                AlignedByteOffset = (uint)Slot.AllocatedBytes,
                InputSlotClass = InputSlotClass,
                InstanceDataStepRate = (uint)InputDataStepRate,
              });
              Slot.AllocatedBytes += TypeInfo.SizeInBytes;
            } else throw new NotSupportedException();
          }
        }
        var passDesc = Technique.GetPassByIndex(0).Description;
        InputLayout = Device.CreateInputLayout(
            Layout.ToArray(),
            passDesc.InputAssemblerInputSignature,
            passDesc.InputAssemblerInputSignatureSize);
      }
      //var Buffers = new D3DBuffer[1];

      D3DBuffer IndexBuffer = null;


      if (Buffer is IUserBuffer) {
        var Buffer0 = (IUserBuffer)Buffer;
        (Buffer0.VertexCount > 0).Assert();
        (Buffer0.InstanceCount > 0).Assert();

        // Take care of index buffer first.
        uint IndexCount;
        if (Buffer0.BaseIndices != null) {
          var Indices = Buffer0.BaseIndices.ToArray();
          IndexCount = (uint)Indices.Length;
          if (Resources.ContainsKey(Buffer0.BaseIndices)) {
            IndexBuffer = (D3DBuffer)Resources[Buffer0.BaseIndices];
          } else {
            IntPtr IndicesData = Marshal.AllocCoTaskMem(Indices.Length * sizeof(int));
            Marshal.Copy(Indices, 0, IndicesData, Indices.Length);
            BufferDescription bufferDesc = new BufferDescription() {
              Usage = Usage.Default,
              ByteWidth = (uint)(Indices.Length * sizeof(int)),
              BindFlags = BindFlag.IndexBuffer,
              CpuAccessFlags = 0,
              MiscFlags = 0
            };
            SubresourceData initData = new SubresourceData() {
              SysMem = IndicesData
            };
            IndexBuffer = Device.CreateBuffer(bufferDesc, initData);
            Marshal.FreeCoTaskMem(IndicesData);
            Resources[Buffer0.BaseIndices] = IndexBuffer;
          }
        } else IndexCount = 0;

        var VertexId = new LoopIndexExpr(Buffer0.VertexCount) { Name = "VertexId" };
        var InstanceId = new LoopIndexExpr(Buffer0.InstanceCount) { Name = "InstanceId" };
        var InputBindings = Buffer0.BaseF(VertexId, InstanceId);

        for (int j = 0; j < Slots.Elements.Count; j++) {

          var Array = linq.Expression.Parameter(typeof(int), "Memory");
          var Eval = new UniformEval(this) { Technique = Technique };
          var loop = Eval.For(j == 0 ? VertexId : InstanceId, (Index) => {
            int ByteOffset = 0;
            var Blocks = new linq.Expression[Slots[j].Properties.Count];
            for (int i = 0; i < Slots[j].Properties.Count; i++) {
              var p = Slots[j].Properties[i];
              var RHS = Eval.DoEval(InputBindings[p.Name]).Value;

              var Array0 = linq.Expression.Add(Array, linq.Expression.Multiply(Index, 
                linq.Expression.Constant(Slots[j].AllocatedBytes, typeof(int))));
              Array0 = linq.Expression.Add(Array0, linq.Expression.Constant(ByteOffset, typeof(int)));
              var Array1 = linq.Expression.New(typeof(IntPtr).GetConstructor(new Type[] { typeof(int) }), Array0);
              var TypeInfo = p.Type.FormatForType();
              ByteOffset += TypeInfo.SizeInBytes;
              var TypeK = TypeInfo.TypeK;
              TypeK = (TypeK == typeof(bool)) ? typeof(int) : TypeK;
              var TempArray = linq.Expression.Parameter(TypeK.MakeArrayType(), "TempArray");
              var Assigns = new linq.Expression[TypeInfo.Arity.Count + 2];
              Assigns[0] = linq.Expression.Assign(TempArray,
                linq.Expression.NewArrayBounds(TypeK, linq.Expression.Constant(TypeInfo.Arity.Count, typeof(int))));
              for (int k = 0; k < TypeInfo.Arity.Count; k++) {
                var AccessA = linq.Expression.ArrayAccess(TempArray, linq.Expression.Constant(k, typeof(int)));
                var AccessB = TypeInfo.Arity.PropertyFor(k)(RHS);
                if (TypeInfo.Arity.TypeOfK != TypeK) {
                  if (TypeInfo.Arity.TypeOfK == typeof(bool) && TypeK == typeof(float)) {
                    AccessB = linq.Expression.Condition(AccessB, linq.Expression.Constant(1f, typeof(float)),
                                                                 linq.Expression.Constant(0f, typeof(float)));
                  } else AccessB = linq.Expression.Convert(AccessB, TypeK);
                }
                Assigns[k + 1] = linq.Expression.Assign(AccessA, AccessB);
              }
              var CopyMethod = typeof(Marshal).GetMethod("Copy", new Type[] { 
                TypeK.MakeArrayType(), 
                typeof(Int32), 
                typeof(IntPtr), 
                typeof(Int32) 
              });
              Assigns[TypeInfo.Arity.Count + 1] =
                linq.Expression.Call(
                //typeof(Marshal), "Copy", new Type[] {}, 
                  CopyMethod,
                  linq.Expression.Convert(TempArray, TypeK.MakeArrayType()),
                  linq.Expression.Constant(0, typeof(Int32)),
                  linq.Expression.Convert(Array1, typeof(IntPtr)),
                  linq.Expression.Constant(TypeInfo.Arity.Count, typeof(Int32)));
              Blocks[i] = linq.Expression.Block(new linq.ParameterExpression[] { TempArray }, Assigns);
            }
            return linq.Expression.Block(Blocks);
          });
          var InitF = linq.Expression.Lambda<Action<int>>(loop, Array).Compile();
          var InitialData = new SubresourceData() { SysMem = Marshal.AllocCoTaskMem((int)Slots[j].AllocatedBytes * 
            (j == 0 ? Buffer0.VertexCount : Buffer0.InstanceCount)) };
          InitF(InitialData.SysMem.ToInt32());

          BufferDescription BufferDesc = new BufferDescription() {
            Usage = Usage.Default,
            ByteWidth = (uint)(Slots[j].AllocatedBytes * (j == 0 ? Buffer0.VertexCount : Buffer0.InstanceCount)),
            BindFlags = BindFlag.VertexBuffer,
            CpuAccessFlags = 0,
            MiscFlags = 0
          };
          Slots[j].Buffer = Device.CreateBuffer(BufferDesc, InitialData);
          Marshal.FreeCoTaskMem(InitialData.SysMem);
        }
        var VertexCount = (uint)Buffer0.VertexCount;
        var InstanceCount = (uint)Buffer0.InstanceCount;
        if (IndexBuffer == null) {
          if (Buffer0.InstanceCount <= 1) DoDraw = Device0 => Device0.Draw(VertexCount, 0);
          else DoDraw = Device0 => Device0.DrawInstanced(VertexCount, InstanceCount, 0, 0);
        } else {
          if (Buffer0.InstanceCount <= 1) DoDraw = Device0 => Device0.DrawIndexed(IndexCount, 0, 0);
          else DoDraw = Device0 => Device0.DrawIndexedInstanced(IndexCount, InstanceCount, 0, 0, 0);
        }
      } else if (Buffer is IStreamOutBuffer) {
        (Slots.Elements.Count == 1).Assert();
        BufferDescription BufferDesc = new BufferDescription() {
          Usage = Usage.Default,
          ByteWidth = (uint)(Slots[0].AllocatedBytes * ((IStreamOutBuffer)Buffer).MaxCount),
          BindFlags = BindFlag.VertexBuffer | (BindFlag.StreamOutput),
          CpuAccessFlags = 0,
          MiscFlags = 0
        };
        Slots[0].Buffer = Device.CreateBuffer(BufferDesc); // no initial data for stream out.
        DoDraw = Device0 => Device0.DrawAuto(); // nothing to do!
      } else throw new NotSupportedException();

      var tmpS = Buffer.BaseTopology.ToString();
      PrimitiveTopology Topology =
        (PrimitiveTopology) typeof(PrimitiveTopology).GetField(Buffer.BaseTopology.ToString()).GetValue(null);
      var Entry = new BufferEntry() {
        InputLayout = InputLayout,
        Buffers = Slots.Elements.Select<Slot,D3DBuffer>(slot => slot.Buffer).ToArray(),
        IndexBuffer = IndexBuffer,
        Strides = Slots.Elements.Select<Slot,uint>(slot => (uint) slot.AllocatedBytes).ToArray(),
        Topology = Topology,
        RefreshVertices = () => { }, // not yet!
        DoDraw = DoDraw,
      };
      // remember!
      this.Buffers[Buffer] = Entry;
      return Entry;
    }
    private D3DResource CreateTexture(IUserTexture Texture) {
      if (Resources.ContainsKey(Texture)) return Resources[Texture];
      Format Format = (Format)typeof(Format).GetField(Texture.Format.ToString()).GetValue(null);
      IntPtr Mem;
      D3DResource Texture0;
      Action Unmap;
      if (Texture.DimCount == 1) {
        Texture1DDescription textureDescription = new Texture1DDescription() {
          Width = (uint)Texture.DimSizes[0],
          ArraySize = 1,
          MipLevels = 1,
          Format = Format,
          Usage = Usage.Dynamic,
          CpuAccessFlags = CpuAccessFlag.Write,
          BindFlags = BindFlag.ShaderResource,
          /*
          SampleDescription = new SampleDescription() {
            Count = 1,
            Quality = 0,
          },
           */
        };
        var Texture1 = Device.CreateTexture1D(textureDescription);
        Texture0 = Texture1;
        Mem = Texture1.Map(0, Map.WriteDiscard, MapFlag.Unspecified);
        Unmap = () => Texture1.Unmap(0);
      } else if (Texture.DimCount == 2) {
        Texture2DDescription textureDescription = new Texture2DDescription() {
          Width = (uint)Texture.DimSizes[0],
          Height = (uint)Texture.DimSizes[1],
          ArraySize = 1,
          MipLevels = 1,
          Format = Format,
          Usage = Usage.Dynamic,
          CpuAccessFlags = CpuAccessFlag.Write,
          BindFlags = BindFlag.ShaderResource,
          SampleDescription = new SampleDescription() {
            Count = 1,
            Quality = 0,
          },
        };
        var Texture1 = Device.CreateTexture2D(textureDescription);
        Texture0 = Texture1;
        MappedTexture2D texmap = Texture1.Map(0, Map.WriteDiscard, MapFlag.Unspecified);
        Mem = texmap.Data; // Marshal.AllocCoTaskMem(Size * sizeof(float) * Format0.ChannelCount);
        Unmap = () => Texture1.Unmap(0);
      } else if (Texture.DimCount == 3) {
        Texture3DDescription textureDescription = new Texture3DDescription() {
          Width = (uint)Texture.DimSizes[0],
          Height = (uint)Texture.DimSizes[1],
          Depth = (uint) Texture.DimSizes[2],
          MipLevels = 1,
          Format = Format,
          Usage = Usage.Dynamic,
          CpuAccessFlags = CpuAccessFlag.Write,
          BindFlags = BindFlag.ShaderResource,
        };
        var Texture1 = Device.CreateTexture3D(textureDescription);
        Texture0 = Texture1;
        MappedTexture3D texmap = Texture1.Map(0, Map.WriteDiscard, MapFlag.Unspecified);
        Mem = texmap.Data; // Marshal.AllocCoTaskMem(Size * sizeof(float) * Format0.ChannelCount);
        Unmap = () => Texture1.Unmap(0);
      } else throw new NotSupportedException();
      {
        int Size = Texture.DimSizes[0];
        for (int i = 1; i < Texture.DimCount; i++) Size *= Texture.DimSizes[i];
        var Eval = new UniformEval(this) { Technique = null };
        var Format0 = (TextureFormat.Typical)Texture.Format;
        (Format0.Width == TextureFormat.ChannelWidth.WORD).Assert();
        var CopyMethod = typeof(Marshal).GetMethod("Copy", new Type[] { 
                typeof(float).MakeArrayType(), 
                typeof(Int32), 
                typeof(IntPtr), 
                typeof(Int32) 
              });

        //(Texture.Format.
        var loop = Eval.For("i", new Constant<int>(Size), loopI => {
          IntBl loopI0 = new LinqExpr<int>(loopI.Value);
          IntBl[] Indices = new IntBl[Texture.DimCount];
          IntBl Left = loopI0;
          for (int i = 0; i < Texture.DimCount; i++) {
            Indices[i] = Left % Texture.DimSizes[i];
            Left = Left / Texture.DimSizes[i];
          }
          var clr = Texture.Init(Indices);
          linq.ParameterExpression paramArray;
          var assigns = new linq.Expression[Format0.ChannelCount + 2];
          {
            var tempArray = linq.Expression.NewArrayBounds(typeof(float), linq.Expression.Constant(Format0.ChannelCount, typeof(int)));
            paramArray = linq.Expression.Parameter(typeof(float[]), "tempArray");
            assigns[0] = linq.Expression.Assign(paramArray, tempArray);
          }
          for (int i = 0; i < Format0.ChannelCount; i++) {
            var rhs = Eval.DoEval(clr[i].Provider).Value;
            rhs = linq.Expression.Convert(rhs, typeof(float));
            //rhs = linq.Expression.Multiply(rhs, linq.Expression.Constant(float.MaxValue, typeof(float)));
            assigns[i + 1] = linq.Expression.Assign(
              linq.Expression.ArrayAccess(paramArray, linq.Expression.Constant(i, typeof(int))), rhs);
          }
          {
            var start = (loopI0 * Format0.ChannelCount * 4);
            start = start + Mem.ToInt32();
            var address = Eval.DoEval(start.Provider).Value;
            address = linq.Expression.New(typeof(IntPtr).GetConstructor(new Type[] { typeof(int) }), address);
            assigns[Format0.ChannelCount + 1] = linq.Expression.Call(
              CopyMethod,
              paramArray,
              linq.Expression.Constant(0, typeof(Int32)),
              address,
              linq.Expression.Constant(Format0.ChannelCount, typeof(int)));
          }
          return linq.Expression.Block(new linq.ParameterExpression[] { paramArray }, assigns);
        });
        var lambda = linq.Expression.Lambda<Action>(loop);
        lambda.Compile()();
        Unmap();
      }
      Resources[Texture] = Texture0;
      return Texture0;
    }

    private D3DResource CreateTexture(IRenderTargetTexture Texture) {
      if (Resources.ContainsKey(Texture)) return (Texture2D)Resources[Texture];
      Format Format = (Format) typeof(Format).GetField(Texture.Format.ToString()).GetValue(null);
      D3DResource Texture0;
      if (Texture.DimCount == 2) {
        Texture2DDescription textureDescription = new Texture2DDescription() {
          Width = (uint)Texture.DimSizes[0],
          Height = (uint)Texture.DimSizes[1],
          ArraySize = 1,
          MipLevels = 1,
          Format = Format,
          Usage = Usage.Default ,
          BindFlags = BindFlag.ShaderResource | BindFlag.RenderTarget,
          SampleDescription = new SampleDescription() {
            Count = 1,
            Quality = 0,
          },
        };
        Texture0 = Device.CreateTexture2D(textureDescription);
      } else throw new NotSupportedException();
      Resources[Texture] = Texture0;
      {
        var entry = new RenderTargetEntry();
        var descr = new RenderTargetViewDescription() {
          Format = Format,
        };
        if (Texture.DimCount == 2) {
          descr.ViewDimension = RenderTargetViewDimension.Texture2D;
          descr.Texture2D.MipSlice = 0;
        } else throw new NotSupportedException();

        entry.RenderTargetView = Device.CreateRenderTargetView(Texture0, descr);
        entry.Viewport = new Viewport() {
          Width = (uint) Texture.DimSizes[0],
          Height = (uint) Texture.DimSizes[1],
          MinDepth = 0.0f,
          MaxDepth = 1.0f,
          TopLeftX = 0,
          TopLeftY = 0
        };
        Device.ClearRenderTargetView(entry.RenderTargetView, BlackColor);

        RenderTargets[Texture.AsRenderTarget] = entry;
      }
      return Texture0;
    }

    /// <summary>
    /// Creates a ShaderResourceView from a bitmap in a Stream. 
    /// </summary>
    /// <param name="device">The Direct3D device that will own the ShaderResourceView</param>
    /// <param name="stream">Any Windows Imaging Component decodable image</param>
    /// <returns></returns>
    private ShaderResourceView LoadImage(IImageTexture Image) {
      Texture2D texture;
      if (!Resources.ContainsKey(Image)) {
        using (var stream = System.Windows.Application.GetResourceStream(Image.URI).Stream) {
          ImagingFactory factory = new ImagingFactory();
          BitmapDecoder bitmapDecoder =
              factory.CreateDecoderFromStream(
                  stream,
                  DecodeMetadataCacheOptions.OnDemand);

          if (bitmapDecoder.FrameCount == 0)
            throw new ArgumentException("Image file successfully loaded, but it has no image frames.");

          BitmapFrameDecode bitmapFrameDecode = bitmapDecoder.GetFrame(0);
          BitmapSource bitmapSource = bitmapFrameDecode.ToBitmapSource();
          // Create a format converter
          WICFormatConverter converter = factory.CreateFormatConverter();
          converter.Initialize(
              bitmapSource,
              PixelFormats.Pf32bppRGBA,
              BitmapDitherType.None,
              BitmapPaletteType.Custom);

          // get bitmap data
          byte[] buffer = converter.CopyPixels();

          // create texture description
          Texture2DDescription textureDescription = new Texture2DDescription() {
            Width = bitmapSource.Size.Width,
            Height = bitmapSource.Size.Height,
            ArraySize = 1,
            Format = Format.R8G8B8A8_UNORM,
            SampleDescription = new SampleDescription() {
              Count = 1,
              Quality = 0,
            },
            BindFlags = BindFlag.ShaderResource,
            MipLevels = 1,
            CpuAccessFlags = CpuAccessFlag.Write,
            Usage = Usage.Dynamic,
            MiscFlags = 0
          };
          // create texture
          texture = Device.CreateTexture2D(textureDescription);
          // Copy bitmap data to texture
          MappedTexture2D texmap = texture.Map(0, Map.WriteDiscard, MapFlag.Unspecified);
          Marshal.Copy(buffer, 0, texmap.Data, buffer.Length);
          texture.Unmap(0);
          Resources[Image] = texture;
        }
      } else texture = (Texture2D)Resources[Image];
      return ResourceViewFor(texture);
    }
    public ShaderResourceView ResourceViewFor(D3DResource Texture) {
      if (Views.ContainsKey(Texture)) return (ShaderResourceView) Views[Texture];
      ShaderResourceViewDescription srvDescription;
      if (Texture is Texture2D) {
        var Texture0 = (Texture2D)Texture;
        var textureDescription = Texture0.Description;
        srvDescription = new ShaderResourceViewDescription() {
          Format = textureDescription.Format,
          ViewDimension = ShaderResourceViewDimension.Texture2D,
          Texture2D = new Texture2DShaderResourceView() {
            MipLevels = textureDescription.MipLevels,
            MostDetailedMip = 0
          }
        };
      } else if (Texture is Texture1D) {
        var Texture0 = (Texture1D)Texture;
        var textureDescription = Texture0.Description;
        srvDescription = new ShaderResourceViewDescription() {
          Format = textureDescription.Format,
          ViewDimension = ShaderResourceViewDimension.Texture1D,
          Texture1D = new Texture1DShaderResourceView() {
            MipLevels = textureDescription.MipLevels,
            MostDetailedMip = 0
          }
        };
      } else if (Texture is Texture3D) {
        var Texture0 = (Texture3D)Texture;
        var textureDescription = Texture0.Description;
        srvDescription = new ShaderResourceViewDescription() {
          Format = textureDescription.Format,
          ViewDimension = ShaderResourceViewDimension.Texture3D,
          Texture3D = new Texture3DShaderResourceView() {
            MipLevels = textureDescription.MipLevels,
            MostDetailedMip = 0
          }
        };
      } else throw new NotSupportedException();
      var ret = Device.CreateShaderResourceView(Texture, srvDescription);
      Views[Texture] = ret;
      return ret;
    }
  }
  public struct TypeInfo {
    public int SizeInBytes;
    public Func<int, Format> Format;
    public string CName;
    public enum Kinds {
      Scalar, Vector, Matrix, Resource,
    }
    public VecUtils.Info Arity;
    public Type TypeK;
    public Kinds Kind;
  }
  public static class HostExtensions {
    public static TypeInfo FormatForType(this Type type) {
      if (type.IsSubclassOf(typeof(Texture))) {
        return new TypeInfo() {
          Kind = TypeInfo.Kinds.Resource,
        };
      }
      var arity = VecUtils.Query(type);
      (arity != null).Assert();
      var Kind = (arity.Count == 1) ? TypeInfo.Kinds.Scalar :
        (arity.Dim is IDim2D) ? TypeInfo.Kinds.Matrix : TypeInfo.Kinds.Vector;



      // take the lenght at face value.
      var TypeK = arity.TypeOfK;
      if (TypeK == typeof(double) || arity.Dim is IDim2D) TypeK = typeof(float);

      if (arity.Dim is IDim2D && ((IDim2D)arity.Dim).RowCountI == 1) {
        // convert...
        Kind = TypeInfo.Kinds.Vector;

      }
      string CName;
      if (TypeK == typeof(float)) CName = "Float";
      else if (TypeK == typeof(int)) CName = "Int";
      else if (TypeK == typeof(bool)) CName = "Bool";
      else throw new NotSupportedException();
      int SizeInBytes = arity.Count * 4;
      return new TypeInfo() {
        Kind = Kind,
        SizeInBytes = SizeInBytes,
        CName = CName,
        TypeK = TypeK,
        Arity = arity,
        Format = i => {
          var bytes = i * 4;
          (bytes < SizeInBytes).Assert();
          var R = Math.Min((SizeInBytes - bytes), 16) / 4;
          if (R == 1) {
            if (TypeK == typeof(float)) return Format.R32_FLOAT;
            else if (TypeK == typeof(int)) return Format.R32_SINT;
            else if (TypeK == typeof(bool)) return Format.R32_UINT;
            else throw new NotSupportedException();
          } else if (R == 2) {
            if (TypeK == typeof(float)) return Format.R32G32_FLOAT;
            else if (TypeK == typeof(int)) return Format.R32G32_SINT;
            else if (TypeK == typeof(bool)) return Format.R32G32_UINT;
            else throw new NotSupportedException();
          } else if (R == 3) {
            if (TypeK == typeof(float)) return Format.R32G32B32_FLOAT;
            else if (TypeK == typeof(int)) return Format.R32G32B32_SINT;
            else if (TypeK == typeof(bool)) return Format.R32G32B32_UINT;
            else throw new NotSupportedException();
          } else if (R == 4) {
            if (TypeK == typeof(float)) return Format.R32G32B32A32_FLOAT;
            else if (TypeK == typeof(int)) return Format.R32G32B32A32_SINT;
            else if (TypeK == typeof(bool)) return Format.R32G32B32A32_UINT;
            else throw new NotSupportedException();
          } else throw new NotSupportedException();
        },

      };
    }
  }
}
